package com.niodata.dp.plugin.core;

import com.niodata.dp.util.DateParser;
import com.niodata.dp.util.JsonUtil;
import com.niodata.dp.util.TimeUtil;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.BiConsumer;


public class PluginConfigParser {

  /**
   * parse plugin config.
   *
   * @param clazz config class
   * @param <T> config bean
   * @return T
   */
  public static <T> T parseConfig(Class<T> clazz, Properties properties) throws Exception {
    T t = clazz.newInstance();
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
      InputParam pkgParam = field.getAnnotation(InputParam.class);
      if (pkgParam != null) {
        String value = properties.getProperty(pkgParam.name());
        field.setAccessible(true);
        if (value != null && value.trim().length() > 0) {
          field.set(t,
                convertValue(field, value.trim(), pkgParam.timeFormat(),
                      pkgParam.parseTime(), pkgParam.name()));
        }
      }
    }
    return t;
  }

  /**
   * parse plugin config.
   *
   * @param clazz config class
   * @param <T> config bean
   * @return T
   */
  public static <T> T parseConfig(Class<T> clazz, Map<String, Object> properties) throws Exception {
    T t = clazz.newInstance();
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
      InputParam pkgParam = field.getAnnotation(InputParam.class);
      if (pkgParam != null) {
        Object valueObject = properties.get(pkgParam.name());
        if (valueObject == null || valueObject.toString().trim().length() <= 0) {
          continue;
        }
        field.setAccessible(true);
        if (valueObject != null) {
          Object obj = convertValue(field, valueObject, pkgParam.timeFormat(),
                pkgParam.parseTime(), pkgParam.name());
          field.set(t, obj);
          Class filedClazz = field.getClass();
          if (Map.class.isAssignableFrom(filedClazz)
                || filedClazz.isArray()
                || List.class.isAssignableFrom(filedClazz)) {
            obj = JsonUtil.toJson(obj);
          } else if (filedClazz.equals(Date.class)) {
            obj = TimeUtil.format((Date) obj, pkgParam.timeFormat());
          }
          properties.put(pkgParam.name(), obj);
        }
      }
    }
    return t;
  }


  private static Object convertMap(Field field, Map srcValue, String timeFormat,
        boolean parseTime, String paramName) {
    Map<String, Object> resultMap = new HashMap();
    srcValue.forEach(new BiConsumer<String, Object>() {
      @Override
      public void accept(String s, Object o) {
        String value = s;
        if (parseTime) {
          value = DateParser.replaceDateStartValue(o.toString());
        }
        try {
          Object convertValue = convertValue(String.class, value, timeFormat, parseTime, paramName);
          resultMap.put(s, convertValue);
        } catch (ParseException e) {
          e.printStackTrace();
        }
      }
    });
    return resultMap;
  }

  private static List buildList(Field field) {
    String name = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]
          .getTypeName().toLowerCase();
    if (name.indexOf("string") >= 0) {

      return new ArrayList<String>();
    }
    if (name.indexOf("date") >= 0) {
      return new ArrayList<Date>();
    }

    if (name.indexOf("int") >= 0) {
      return new ArrayList<Integer>();
    }

    if (name.indexOf("float") >= 0) {
      return new ArrayList<Float>();
    }

    if (name.indexOf("double") >= 0) {
      return new ArrayList<Double>();
    }

    if (name.indexOf("boolean") >= 0) {
      return new ArrayList<Boolean>();
    }
    return new ArrayList();
  }

  private static <T> T[] buildArray(Field field, int length) {
    String name = field.getType().getComponentType().getName().toLowerCase();
    if (name.indexOf("string") >= 0) {

      return (T[]) new String[length];
    }
    if (name.indexOf("date") >= 0) {
      return (T[]) new Date[length];
    }

    if (name.indexOf("int") >= 0) {
      return (T[]) new Integer[length];
    }

    if (name.indexOf("float") >= 0) {
      return (T[]) new Float[length];
    }

    if (name.indexOf("double") >= 0) {
      return (T[]) new Double[length];
    }

    if (name.indexOf("boolean") >= 0) {
      return (T[]) new Boolean[length];
    }
    return null;
  }


  private static Object convertList(Field field, List srcValue, String timeFormat,
        boolean parseTime, String paramName)
        throws IOException, ParseException {
    Type clazz = null;
    if (field.getType().isArray()) {
      clazz = field.getType().getComponentType();
    } else {
      clazz = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
    }
    List distList = new ArrayList(srcValue.size());
    if (distList.size() > 0 && clazz.equals(Object.class) && distList.get(0) != null) {
      clazz = distList.get(0).getClass();
    }
    for (Object v : srcValue) {
      Object value = convertValue(clazz, v.toString(), timeFormat, parseTime, paramName);
      distList.add(value);
    }
    if (field.getType().isArray()) {
      Object[] arrayv = buildArray(field, distList.size());
      if (arrayv == null) {
        arrayv = new Object[0];
      }
      return distList.toArray(arrayv);
    } else {
      List list = buildList(field);
      list.addAll(distList);
      return list;
    }
  }

  private static Object convertValue(Type type, Object srcValue,
        String timeFormat, boolean parseTime, String paramName)
        throws ParseException {
    String name = type.getTypeName().toLowerCase();
    String srcStringValue = srcValue.toString().trim();
    if (name.indexOf("string") >= 0) {
      if (parseTime) {
        return DateParser.replaceDateStartValue(srcStringValue);
      }
      return srcStringValue;
    }
    if (name.indexOf("date") >= 0 && srcStringValue.length() > 0) {
      SimpleDateFormat sdf = new SimpleDateFormat(timeFormat);
      return sdf.parse(srcStringValue);
    }

    if (name.indexOf("int") >= 0 && srcStringValue.length() > 0) {
      return Integer.valueOf(srcStringValue);
    }

    if (name.indexOf("float") >= 0 && srcStringValue.length() > 0) {
      return Float.valueOf(srcStringValue);
    }

    if (name.indexOf("double") >= 0 && srcStringValue.length() > 0) {
      return Double.valueOf(srcStringValue);
    }

    if (name.indexOf("boolean") >= 0 && srcStringValue.length() > 0) {
      return Boolean.valueOf(srcStringValue);
    }

    if (name.indexOf("object") >= 0) {
      return srcValue;
    }

    throw new RuntimeException("unknown config datatype or value is empty:"
          + paramName + ", " + type);
  }

  private static Object convertValue(Field field, Object srcValue, String timeFormat,
        boolean parseTime, String paramName)
        throws Exception {
    if (srcValue == null) {
      return null;
    }
    boolean isArray = field.getType().isArray()
          || List.class.isAssignableFrom(field.getType());
    if (isArray) {
      if (srcValue instanceof String) {
        if (srcValue.toString().length() == 0) {
          return Collections.emptyList();
        }
        srcValue = JsonUtil.fromJsonList(String.class, srcValue.toString());
      }
      return convertList(field, (List) srcValue, timeFormat, parseTime, paramName);
    }

    boolean isMap = Map.class.isAssignableFrom(field.getType());
    if (isMap) {
      if (srcValue instanceof String) {
        if (srcValue.toString().length() == 0) {
          return Collections.emptyMap();
        }
        srcValue = JsonUtil.fromJsonMap(String.class, String.class, srcValue.toString());
      }
      return convertMap(field, (Map) srcValue, timeFormat, parseTime, paramName);
    }
    return convertValue(field.getGenericType(), srcValue, timeFormat, parseTime, paramName);
  }

}
